10x Home

Introduction

Welcome to the 10x Genomics SIB Days 2020 - virtual conference Visium Spatial Transcriptomics workshop!

The purpose of this tutorial is to walk users through some of the steps necessary to explore data produced by the 10x Genomics Visium Spatial Gene Expression Solution and the Spaceranger pipeline. All datasets that we will investigate to day are all freely available from 10x Genomics.

Please note that this tutorial is largely an extension off of the primary Seurat Visium Tutorial

Things to know about this workshop

  1. All files that will be used can be found at: /mnt/libs/shared_data/

[Terminal]

  1. Getting started with R and Visium data outside of Seurat at: https://support.10xgenomics.com/spatial-gene-expression/software/pipelines/latest/rkit
  2. Reference genome for all samples is GRCh38/mm10
  3. All 10x software including Spaceranger, and Loupe Browser, can be downloaded from the 10x Support Site
  4. Most Seurat figures can be controlled with patchwork because they are fundamentally ggplot objects. Some really nice examples can be found on the Patchwork GitHub
  5. This tutorial is NOT officially supported by 10x genomics. It is simply for informative purposes and to provide the groundwork for individuals to get started with 3rd party analysis tools.

Exploring Visium Data with Seurat

Load our packages

First we’ll set the libPath to make sure we are all using the same set of pre-installed libraries. for 10x

.libPaths(new = c("/mnt/home/stephen.williams/R/x86_64-conda_cos6-linux-gnu-library/3.6",
                  "/mnt/opt/R/R-3.6.1-conda-openblas/R-library" ,                        
                  "/mnt/opt/R/R-3.6.1-conda-openblas/env/lib/R/library"))

for tutorial

.libPaths(new = "/usr/lib64/R/library")
library(Seurat)
library(ggplot2)
library(patchwork)
library(dplyr)
library(RColorBrewer)

Loading data in a Seurat object

Now we’ll load up the dataset that will be used from this point forward using Seurat::Load10X_Spatial function.

Real Dataset for the tutorial

breast_cancer <- Load10X_Spatial(data.dir = "/mnt/libs/shared_data/human_breast_cancer_1/outs/",
                filename = "V1_Breast_Cancer_Block_A_Section_1_filtered_feature_bc_matrix.h5")

Same data just internal to 10x

breast_cancer <- Load10X_Spatial(data.dir = "/mnt/analysis/marsoc/pipestances/HWHTFDSXX/SPATIAL_RNA_COUNTER_PD/163086/HEAD/outs/", slice = "slice1")

Note that the Default Assay is set to “Spatial”

DefaultAssay(breast_cancer)
[1] "Spatial"

It’s good to note that there are a bunch of Visium data sets hosted by the Satija lab in the Seurat Data Package.

Results

QC

It’s very easy to add metadata to your Seurat object with any values you want to check out on top of the defaults.

mito.gene.names <- grep("^mt-", rownames(breast_cancer@assays$Spatial), value = TRUE, ignore.case = TRUE)
col.total <- Matrix::colSums(breast_cancer@assays$Spatial)
breast_cancer <- AddMetaData(breast_cancer, Matrix::colSums(breast_cancer@assays$Spatial[mito.gene.names, ]) / col.total, "pct.mito")

Let’s have a look at some basic QC information. Keep in mind that most Seurat plots are ggplot object and can be manipulated as such.

Counts = UMI

Features = Genes

plot1 <- VlnPlot(breast_cancer, features = "nCount_Spatial", pt.size = 0.1) + 
  ggtitle("UMI") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot2 <- VlnPlot(breast_cancer, features = "nFeature_Spatial", pt.size = 0.1) + 
  ggtitle("Genes") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot3 <- VlnPlot(breast_cancer, features = "pct.mito", pt.size = 0.1) + 
  ggtitle("Percentage Mito genes") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()
plot4 <- SpatialFeaturePlot(breast_cancer, features = "nCount_Spatial") + 
  theme(legend.position = "right")

plot5 <- SpatialFeaturePlot(breast_cancer, features = "nFeature_Spatial") +
  theme(legend.position = "right")
plot6 <- SpatialFeaturePlot(breast_cancer, features = "pct.mito") +
  theme(legend.position = "right")

plot1 + plot2 + plot3  + plot4 + plot5 + plot6 + plot_layout(nrow = 2, ncol = 3)

Normalization

Spaceranger does UMI normalization for clustering and differential expression but does not return that normalized matrix.

Let’s have a look at pre-normalization raw UMI counts. Feel free to change these genes or add genes.

SpatialFeaturePlot(breast_cancer, features = c("ERBB2", "CD8A", "MT-ND1"))

SE transform

  • This will take ~3-4 min.

Don’t worry about reachediteration limit warnings. See https://github.com/ChristophH/sctransform/issues/25 for discussion

Default assay will now be set to SCT

breast_cancer <- SCTransform(breast_cancer, assay = "Spatial", verbose = FALSE)

Now let’s have a look at SCT normalized UMI counts for these same genes. The Default Assays is now “SCT”

SpatialFeaturePlot(breast_cancer, features = c("ERBB2", "CD8A"))

From Seurat:

The default parameters in Seurat emphasize the visualization of molecular data. However, you can also adjust the size of the spots (and their transparency) to improve the visualization of the histology image, by changing the following parameters:

  • pt.size.factor- This will scale the size of the spots. Default is 1.6
  • alpha - minimum and maximum transparency. Default is c(1, 1).
  • Try setting to alpha c(0.1, 1), to downweight the transparency of points with lower expression
p1 <- SpatialFeaturePlot(breast_cancer, features = "IGFBP5", pt.size.factor = 1)+ 
  theme(legend.position = "right") +
  ggtitle("Actual Spot Size")
p2 <- SpatialFeaturePlot(breast_cancer, features = "IGFBP5")+ 
  theme(legend.position = "right") +
  ggtitle("Scaled Spot Size")
p1 + p2

Dimensionality reduction, clustering, and visualization

We can then proceed to run dimensionality reduction and clustering on the RNA expression data, using the same workflow as we use for scRNA-seq analysis.

Some of these processes can be parallized please see Parallelization in Seurat for more info

The default UMAP calculation is performed with the R-based UWOT library However, you can run UMAP in python via the reticulate library and umap-learn. We have found that for smaller data sets (<= 10k cells/spots) UWOT is great. For much larger data sets (100k + cells/spots) umap-learn can be a faster option.

Dimensionality reduction

First Let’s Run our PCA. How many PCs should we use going forward?

breast_cancer <- RunPCA(breast_cancer, assay = "SCT", verbose = FALSE)
breast_cancer <- FindVariableFeatures(breast_cancer)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
ElbowPlot(breast_cancer, ndims = 40)

Clustering

Now let’s cluster and project to UMAP. Does 30 PCs look okay? What if we changed the number of dimensions to 20?

breast_cancer <- FindNeighbors(breast_cancer, reduction = "pca", dims = 1:30)
Computing nearest neighbor graph
Computing SNN
breast_cancer <- FindClusters(breast_cancer, verbose = FALSE)
breast_cancer <- RunUMAP(breast_cancer, reduction = "pca", dims = 1:30)
11:29:34 UMAP embedding parameters a = 0.9922 b = 1.112
11:29:34 Read 3822 rows and found 30 numeric columns
11:29:34 Using Annoy for neighbor search, n_neighbors = 30
11:29:34 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:29:35 Writing NN index file to temp file /tmp/RtmpRqpjKF/file4d0f925f1c450
11:29:35 Searching Annoy index using 1 thread, search_k = 3000
11:29:36 Annoy recall = 100%
11:29:37 Commencing smooth kNN distance calibration using 1 thread
11:29:37 Initializing from normalized Laplacian + noise
11:29:41 Commencing optimization for 500 epochs, with 157848 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:29:50 Optimization finished

Now let’s have a look at the clustering in UMAP space

p1 <- DimPlot(breast_cancer, reduction = "umap", label = FALSE) +
  labs(color = "Cluster")

p2 <- FeaturePlot(breast_cancer, features = c('nFeature_Spatial','pct.mito'), dims = 1:2)

p1 / p2 

Here’s the clustering in UMAP and image space

p1 <- DimPlot(breast_cancer, reduction = "umap", label = TRUE) +
  labs(color = "Cluster")
p2 <- SpatialDimPlot(breast_cancer, label = TRUE, label.size = 3) +
  labs(fill = "Cluster")

p1 + p2 + plot_annotation(
  title = 'Clustering in UMAP and Tissue Space',
  caption = 'Processed by Spaceranger 1.1\nNormalization and Clustering by Seurat'
) + plot_layout(nrow = 1)

I don’t really like these colors so let’s change them manually

p1 <- DimPlot(breast_cancer, reduction = "umap", label = TRUE) +
  labs(color = "Cluster") + 
  scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))

p2 <- SpatialDimPlot(breast_cancer, label = TRUE, label.size = 3) +
  labs(fill = "Cluster")+ 
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))

p1 + p2 + plot_annotation(
  title = 'Clustering in UMAP and Tissue Space',
  caption = 'Processed by Spaceranger 1.1\nNormalization and Clustering by Seurat'
) + plot_layout(nrow = 1)

If interested you can also now look at UMI and Gene counts per cluster as well

plot1 <- VlnPlot(breast_cancer, features = "nCount_Spatial", pt.size = 0.1) + 
  ggtitle("UMI") +
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))+
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot2 <- VlnPlot(breast_cancer, features = "nFeature_Spatial", pt.size = 0.1) + 
  ggtitle("Genes") +
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green"))+
  theme(axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot1 + plot2

We can also look at some of our QC information by cluster now that we’ve processed the data

plot1 <- VlnPlot(breast_cancer, features = "nCount_Spatial", pt.size = 0.1) + 
  ggtitle("UMI") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot2 <- VlnPlot(breast_cancer, features = "nFeature_Spatial", pt.size = 0.1) + 
  ggtitle("Genes") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()

plot3 <- VlnPlot(breast_cancer, features = "pct.mito", pt.size = 0.1) + 
  ggtitle("Percentage Mito genes") +
  theme(axis.text.x = element_blank(), 
        axis.title.x = element_blank(), 
        legend.position = "right") +
  NoLegend()
plot4 <- SpatialFeaturePlot(breast_cancer, features = "nCount_Spatial") + 
  theme(legend.position = "right")

plot5 <- SpatialFeaturePlot(breast_cancer, features = "nFeature_Spatial") +
  theme(legend.position = "right")
plot6 <- SpatialFeaturePlot(breast_cancer, features = "pct.mito") +
  theme(legend.position = "right")

plot1 + plot2 + plot3  + plot4 + plot5 + plot6 + plot_layout(nrow = 2, ncol = 3)

Now let’s take a look at at a gene of interest with violin plots but also in image space. The triangles represent the mean expression of each cluster.

p1 <- VlnPlot(breast_cancer, features = "IGFBP5", pt.size = 0.1) + 
              ggtitle("IGFBP5") +
              scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                                           "#a65628", "#999999", "black", "pink", "purple", "brown",
                                           "grey", "yellow", "green"))+
              theme(axis.title.x = element_blank(), 
                    legend.position = "right") +
              NoLegend() +
              stat_summary(fun=mean, geom="point", shape=23, size=4, color="red")

p2 <- SpatialFeaturePlot(breast_cancer, features = "IGFBP5")+ 
  theme(legend.position = "right")

p3 <- SpatialDimPlot(breast_cancer, label = TRUE, label.size = 3) +
  labs(fill = "Cluster")+ 
  scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green")) +
  NoLegend()

row1 <- p2 + p3 + plot_layout(nrow = 1)

row1 + p1+ plot_layout(nrow = 2, widths = c(0.5, 0.5))

We can also look at these data interactively. This function can be a little slow but also very useful to visualize expression in different projection spaces. We won’t run this today

LinkedDimPlot(breast_cancer)

Spatially variable features

First we’ll identify deferentially expressed genes. Let’s find all the markers for every cluster. We’ve already pre calculated these for you so let’s just load them up. workshop

de_markers <- readRDS(file = "/mnt/libs/shared_data/de_markers.rds")

de_markers %>%
  group_by(cluster) %>% 
  top_n(n = 2, wt = avg_logFC)

10x

de_markers <- readRDS(file = "/mnt/home/stephen.williams/yard/Odin/SIB_2020_Workshop/de_markers.rds")

de_markers %>%
  group_by(cluster) %>% 
  top_n(n = 2, wt = avg_logFC)

Originally this was processed with

de_markers <- FindAllMarkers(breast_cancer, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)

Identify the most up-regulated and down-regulated genes

de_markers_up <- de_markers %>%
  arrange(-avg_logFC)

de_markers_down <- de_markers %>%
  arrange(avg_logFC)
de_markers_up
de_markers_down

Most up-regulated genes

SpatialFeaturePlot(object = breast_cancer, features = de_markers_up$gene[1:13], alpha = c(0.1, 1), ncol = 3)

Most down-regulated genes

SpatialFeaturePlot(object = breast_cancer, features = de_markers_down$gene[1:13], alpha = c(0.1, 1), ncol = 3)

What are the top variable features?

VariableFeatures(breast_cancer)[1:10]
 [1] "IGLC2" "ALB"   "IGHG3" "IGHM"  "IGHA1" "IGKC"  "IGHG1" "IGLC3" "IGHG2" "CPB1" 

What are the top DE genes?

rownames(de_markers)[1:10]
 [1] "IGHG1" "IGKC"  "IGHG3" "IGLC2" "IGHG4" "C3"    "CYBA"  "IGLC3" "SFRP2" "TIMP1"

Spatially Variable Genes

So what about spatial enrichment? This can be a very informative analysis tool that takes into the spatial relationship of each gene.

Some methods for these approaches are:

  1. Trendsceek
  2. Splotch
  3. SPARK
  4. SpatialDE
  • We have found this implementation not to be very effective. It’s also not under active development

Using the top 100 variable genes find spatially enriched ones. Note that in the Seurat Spatial Tutorial they use 1000 genes (this can take a long time). You can also use all genes but that will take a long time. Using a calculation of Morans I can sometimes be a faster approach, especially if you are using parallization. Here we’ll do both.

While this process is running it is a good time to take a short couple minute break, catch up, or ask questions.

breast_cancer <- FindSpatiallyVariableFeatures(breast_cancer, 
                                               assay = "SCT", 
                                               slot = "scale.data", 
                                               features = VariableFeatures(breast_cancer)[1:100],
                                               selection.method = "markvariogram", verbose = TRUE)

Have a look at the spatially variable genes calculated by markvariogram ordered from most variable to least variable

SpatiallyVariableFeatures(breast_cancer, selection.method = "markvariogram", decreasing = TRUE)
  [1] "CRISP3"     "CXCL14"     "MGP"        "CPB1"       "SLITRK6"    "TTLL12"     "MALAT1"     "AGR2"       "ALB"       
 [10] "GFRA1"      "S100G"      "CSTA"       "DEGS1"      "TFF3"       "IGLC2"      "IGHG3"      "C6orf141"   "TFF1"      
 [19] "IGKC"       "HEBP1"      "IGHG1"      "APOE"       "ZNF350-AS1" "AC087379.2" "IGHG4"      "C3"         "FCGR3B"    
 [28] "TIMP1"      "LINC00645"  "IGHM"       "SCGB2A2"    "KRT14"      "IGLC3"      "KRT17"      "LYZ"        "APOC1"     
 [37] "SCGB1D2"    "STC2"       "IGHA1"      "C1QA"       "AEBP1"      "APOD"       "KRT5"       "PGM5-AS1"   "MMP7"      
 [46] "CCL19"      "COL6A2"     "TAGLN"      "BGN"        "S100A9"     "IGHG2"      "COL1A2"     "DCN"        "SPP1"      
 [55] "COL1A1"     "CGA"        "VIM"        "IGFBP7"     "FN1"        "CCDC80"     "CXCL9"      "IGHA2"      "TRBC2"     
 [64] "SFRP2"      "CD52"       "KRT6B"      "S100A2"     "LUM"        "COL3A1"     "IGLC7"      "SAA1"       "CARTPT"    
 [73] "COMP"       "S100A8"     "JCHAIN"     "CST1"       "PTGDS"      "SFRP4"      "CD79A"      "CCL21"      "FABP4"     
 [82] "MUC19"      "ACKR1"      "POSTN"      "MMP9"       "S100A7"     "VWF"        "AQP1"       "CTGF"       "A2M"       
 [91] "SPARCL1"    "ACTA2"      "MS4A1"      "IGLL5"      "MYH11"      "CXCL10"     "IGHD"       "HBB"        "MMP1"      
[100] "TPSB2"     
top.features_trendseq <- head(SpatiallyVariableFeatures(breast_cancer, selection.method = "markvariogram"), 8)
SpatialFeaturePlot(breast_cancer, features = top.features_trendseq, ncol = 4, alpha = c(0.1, 1))

Moran’s I implementation. For other spatial data types the x.cuts and y.cuts determines the grid that is laid over the tissue in the capture area. Here we’ll remove those

breast_cancer <- FindSpatiallyVariableFeatures(breast_cancer, 
                                               assay = "SCT", 
                                               slot = "scale.data", 
                                               features = VariableFeatures(breast_cancer)[1:100],
                                               selection.method = "moransi")
Computing Moran's I

Have a look at the spatially variable genes calculated by moransi ordered from most variable to least variable

SpatiallyVariableFeatures(breast_cancer, selection.method = "moransi", decreasing = TRUE)
  [1] "CRISP3"     "CXCL14"     "TTLL12"     "SLITRK6"    "GFRA1"      "AGR2"       "MGP"        "ALB"        "MALAT1"    
 [10] "CPB1"       "DEGS1"      "C6orf141"   "CSTA"       "TFF3"       "LINC00645"  "FCGR3B"     "S100G"      "TFF1"      
 [19] "HEBP1"      "C3"         "ZNF350-AS1" "SCGB1D2"    "APOD"       "IGLC2"      "TIMP1"      "IGHG3"      "SCGB2A2"   
 [28] "APOC1"      "KRT14"      "LYZ"        "CCDC80"     "APOE"       "TAGLN"      "IGHG1"      "AC087379.2" "CCL19"     
 [37] "SPP1"       "PGM5-AS1"   "IGKC"       "S100A9"     "KRT17"      "STC2"       "IGHM"       "FN1"        "KRT5"      
 [46] "IGFBP7"     "C1QA"       "COL6A2"     "AQP1"       "BGN"        "CXCL9"      "ACKR1"      "CARTPT"     "DCN"       
 [55] "AEBP1"      "IGLC3"      "IGHG4"      "VIM"        "S100A2"     "IGHG2"      "MMP7"       "IGHA1"      "CCL21"     
 [64] "KRT6B"      "TRBC2"      "COL1A2"     "CGA"        "SFRP2"      "VWF"        "COL1A1"     "SAA1"       "SPARCL1"   
 [73] "CD52"       "MUC19"      "COL3A1"     "SFRP4"      "S100A8"     "POSTN"      "PTGDS"      "IGHA2"      "JCHAIN"    
 [82] "CD79A"      "ACTA2"      "CTGF"       "LUM"        "CST1"       "A2M"        "S100A7"     "COMP"       "MS4A1"     
 [91] "IGLC7"      "MMP9"       "HBB"        "FABP4"      "MYH11"      "MMP1"       "IGLL5"      "CXCL10"     "TPSB2"     
[100] "IGHD"      
top.features_moransi <- head(SpatiallyVariableFeatures(breast_cancer, selection.method = "moransi"), 8)
SpatialFeaturePlot(breast_cancer, features = top.features_moransi, ncol = 4, alpha = c(0.1, 1))

We can see that the results are slightly different. So let’s take a look at what those difference are

spatially_variable_genes <- breast_cancer@assays$SCT@meta.features %>%
  tidyr::drop_na()

spatially_variable_genes

You can see the two methods show

mm_cor <- cor.test(spatially_variable_genes$moransi.spatially.variable.rank, spatially_variable_genes$markvariogram.spatially.variable.rank)
ggplot(spatially_variable_genes, aes(x=moransi.spatially.variable.rank,y=markvariogram.spatially.variable.rank))+
  geom_point()+
  geom_smooth()+
  xlab("Morans I Rank")+
  ylab("Markvariogram Rank")+
  annotate("text", x = 25, y = 75, label = paste("Pearson's Correlation\n", round(mm_cor$estimate[1], digits = 2), sep = ""))+
  theme_bw()

We can identify these outliers interactively using ggplotly

plotly::ggplotly(
  ggplot(spatially_variable_genes, aes(x=moransi.spatially.variable.rank,y=markvariogram.spatially.variable.rank, label =row.names(spatially_variable_genes)))+
  geom_point()+
  geom_smooth()+
  xlab("Morans I Rank")+
  ylab("Markvariogram Rank")+
  annotate("text", x = 25, y = 75, label = paste("Pearson's Correlation\n", round(mm_cor$estimate[1], digits = 2), sep = ""))+
  theme_bw()
)

Cancer annotations

Where are these genes being expressed relative to pathologist annotation?

plotly::ggplotly(
  ggplot(spatially_variable_genes, aes(x=moransi.spatially.variable.rank,y=markvariogram.spatially.variable.rank, label =row.names(spatially_variable_genes)))+
  geom_point()+
  geom_smooth()+
  xlab("Morans I Rank")+
  ylab("Markvariogram Rank")+
  annotate("text", x = 25, y = 75, label = paste("Pearson's Correlation\n", round(mm_cor$estimate[1], digits = 2), sep = ""))+
  theme_bw()
)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Looks like the Matrix Gla protein ( MGP ) gene is enriched in Ductal Carcinoma In Situ. Not a lot is known about MGP in the context of cancer but it looks like it could be an interesting novel gene to investigate with regard to Ductal Carcinoma In Situ.

ca <- readbitmap::read.bitmap("/mnt/home/stephen.williams/yard/Odin/SIB_2020_Workshop/images/Breast Cancer Path.png")
# in the tutorial
# ca <- readbitmap::read.bitmap('/mnt/libs/shared_data/human_breast_cancer_1/images/Breast_Cancer_Path.png')
plot(0:1,0:1,type="n",ann=FALSE,axes=FALSE)
rasterImage(ca,0,0,1,1)

Single cell/nuclei integration

Here we have a preprocessed Seurat object with 10k nuclei annotated from a breast cancer sample. Don’t bother too much with the details of how this data was generated they don’t particularly matter for our purposes.

Load the Seurat object

10x infrastructure

SpatialFeaturePlot(object = breast_cancer, features = "MGP", alpha = c(0.1, 1), ncol = 3)

For the workshop

bc_snRNA <- readRDS("/mnt/libs/shared_data/bc_snRNA.rds")
bc_snRNA

It’s always a good idea to rerun normalization to make sure your data is in the correct format before moving forward with integration. We’ve already preprocessed this dataset.

bc_snRNA <- SCTransform(bc_snRNA, ncells = 3000, verbose = FALSE) %>% 
  RunPCA(verbose = FALSE) %>% 
  RunUMAP(dims = 1:30)

snRNA Class

bc_snRNA
An object of class Seurat 
56988 features across 10000 samples within 2 assays 
Active assay: SCT (23450 features)
 1 other assay present: RNA
 3 dimensional reductions calculated: pca, harmony, umap

Subclass

DimPlot(bc_snRNA, group.by = "ident", label = FALSE) +
   scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green", "darkgreen"))

snRNA Differentially Expressed Genes

What genes define some cell types?

# Find markers for every cluster compared to all remaining cells, report only the positive ones
# This takes a bit of time so we'll skip it and move on to specific cell types
de_markers_snRNA <- FindAllMarkers(bc_snRNA, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)
de_markers_snRNA %>% 
  group_by(cluster) %>% 
  top_n(n = 2, wt = avg_logFC)

Notice here that we are using test.use = "roc" which is a AUC classifier which will give us an idea as to how well any given gene defines a cell type.

Find markers that define Tumor cells

de_markers_tumor <- FindMarkers(bc_snRNA, ident.1 = "Likely tumor cells", logfc.threshold = 0.25, test.use = "roc", only.pos = TRUE, verbose = FALSE)
de_markers_tumor %>%
  tibble::rownames_to_column("gene") %>% 
  arrange(-power)

Find markers that define T cells

de_markers_tcell <- FindMarkers(bc_snRNA, ident.1 = "T cells", logfc.threshold = 0.25, test.use = "roc", only.pos = TRUE, verbose = FALSE)
de_markers_tcell %>%
  tibble::rownames_to_column("gene") %>% 
  arrange(-power)

Find markers that define stem cells

(FeaturePlot(bc_snRNA, features = "SKAP1")|
 DimPlot(bc_snRNA, group.by = "ident", label = FALSE)+
   scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green", "darkgreen")))

de_markers_stemcell <- FindMarkers(bc_snRNA, ident.1 = "CD49f-hi MaSCs", logfc.threshold = 0.25, test.use = "roc", only.pos = TRUE, verbose = FALSE)
(FeaturePlot(bc_snRNA, features = "IFNG-AS1")|
 DimPlot(bc_snRNA, group.by = "ident", label = FALSE)+
   scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green", "darkgreen")))

Identify and Transfer Anchors

(FeaturePlot(bc_snRNA, features = "IFNG-AS1")|
 DimPlot(bc_snRNA, group.by = "ident", label = FALSE)+
   scale_color_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", 
                               "#a65628", "#999999", "black", "pink", "purple", "brown",
                               "grey", "yellow", "green", "darkgreen")))

Let’s have a look at our annotations again.

anchors <- FindTransferAnchors(reference = bc_snRNA, query = breast_cancer,normalization.method = "SCT")
Performing PCA on the provided reference using 2442 features as input.
Projecting PCA
Finding neighborhoods
Finding anchors
    Found 3276 anchors
Filtering anchors
    Retained 1572 anchors
Extracting within-dataset neighbors
predictions.assay <- TransferData(anchorset = anchors, refdata = bc_snRNA$subclass, prediction.assay = TRUE, 
    weight.reduction = breast_cancer[["pca"]])
Finding integration vectors
Finding integration vector weights
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predicting cell labels
breast_cancer[["predictions"]] <- predictions.assay

DefaultAssay(breast_cancer) <- "predictions"

Immune Microenvironment

T cell subtypes

NOTE we’ll work with subclass

What about the immune microenvironment?

Ducal Carcinoma in situ is depleted of T-cells

ca <- readbitmap::read.bitmap("/mnt/home/stephen.williams/yard/Odin/SIB_2020_Workshop/images/Breast Cancer Path.png")
# in the tutorial
# ca <- readbitmap::read.bitmap('/mnt/libs/shared_data/human_breast_cancer_1/images/Breast_Cancer_Path.png')
plot(0:1,0:1,type="n",ann=FALSE,axes=FALSE)
rasterImage(ca,0,0,1,1)

B cell subtypes

B cells are enriched in the fibrous tissue outside the tumor

(SpatialFeaturePlot(breast_cancer, 
                   features = c("T cells-0"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE) |
SpatialFeaturePlot(breast_cancer, 
                   features = c("T cells-1"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE)) /
(SpatialFeaturePlot(breast_cancer, 
                   features = c("T cells-2"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE) |
SpatialFeaturePlot(breast_cancer, 
                   features = c("T cells-5"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE))

There seem to be some ductal cells but have a look at our score. Are we confident in this assertion?

SpatialFeaturePlot(breast_cancer, 
                   features = c("B cells"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE)

Tumor Subtypes

It looks like the ducal carcinoma in situ is enriched for tumor subtypes 8, 10, and 12 but not 3.

SpatialFeaturePlot(breast_cancer, 
                   features = c("Ductal cells"), 
                   pt.size.factor = 1.5,  crop = TRUE)

Like the Ductal Cells, we might not be as confident in the Tumor Stem Cells but this might make sense considering the 10x snRNA dataset and the Visium dataset are from different individuals.

(SpatialFeaturePlot(breast_cancer, 
                   features = c("Tumor cells-3"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE) |
SpatialFeaturePlot(breast_cancer, 
                   features = c("Tumor cells-8"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE)) /
(SpatialFeaturePlot(breast_cancer, 
                   features = c("Tumor cells-10"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE) |
SpatialFeaturePlot(breast_cancer, 
                   features = c("Tumor cells-12"), 
                   pt.size.factor = 1.5, ncol = 2, crop = TRUE))

LS0tCnRpdGxlOiAiU0lCIERheXMgMjAyMCAtIFZpcnR1YWwgQ29uZmVyZW5jZSAtIEp1bmUgOCwgMjAyMCIKc3VidGl0bGU6ICdEYXRhc2V0OiBCcmVhc3QgQ2FuY2VyJwphdXRob3I6Ci0gUGF0cmljayBSb2VsbGksIE1TYy4sIENvbXB1dGF0aW9uYWwgQmlvbG9naXN0IDIgLSBDb21wdXRhdGlvbmFsIEJpb2xvZ3leWzEweCBHZW5vbWljcywKICBwYXRyaWNrLnJvZWxsaUAxMHhnZW5vbWljcy5jb20gXQotIERyLiBTdGVmYW5pYSBHaWFjb21lbGxvLCBDb21wdXRhdGlvbmFsIEJpb2xvZ2lzdCAyIC0gQ29tcHV0YXRpb25hbCBCaW9sb2d5XlsxMHgKICBHZW5vbWljcywgc3RlZmFuaWEuZ2lhY29tZWxsb0AxMHhnZW5vbWljcy5jb21dCi0gRHIuIFN0ZXBoZW4gV2lsbGlhbXMsIFNlbmlvciBTY2llbnRpc3QgLSBDb21wdXRhdGlvbmFsIEJpb2xvZ3leWzEweCBHZW5vbWljcywgc3RlcGhlbi53aWxsaWFtc0AxMHhnZW5vbWljcy5jb21dCmRhdGU6ICdMYXN0IENvbXBpbGVkOiBgciBmb3JtYXQoU3lzLkRhdGUoKSwgIiVCICVkLCAlWSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUKICAgIHRoZW1lOiBqb3VybmFsCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KClshWzEweCBIb21lXShodHRwczovL2dpdGh1Yi5jb20vc3RlcGhlbndpbGxpYW1zMjIvU0lCXzIwMjBfV29ya3Nob3AvcmF3L21hc3Rlci9pbWFnZXMvMTB4JTIwaG9tZXBhZ2UucG5nKV0oaHR0cHM6Ly93d3cuMTB4Z2Vub21pY3MuY29tLykKCiMgKipJbnRyb2R1Y3Rpb24qKgoKV2VsY29tZSB0byB0aGUgMTB4IEdlbm9taWNzICoqU0lCIERheXMgMjAyMCAtIHZpcnR1YWwgY29uZmVyZW5jZSoqIFZpc2l1bSBTcGF0aWFsIFRyYW5zY3JpcHRvbWljcyB3b3Jrc2hvcCEKClRoZSBwdXJwb3NlIG9mIHRoaXMgdHV0b3JpYWwgaXMgdG8gd2FsayB1c2VycyB0aHJvdWdoIHNvbWUgb2YgdGhlIHN0ZXBzIG5lY2Vzc2FyeSB0byBleHBsb3JlIGRhdGEgcHJvZHVjZWQgYnkgdGhlIDEweCBHZW5vbWljcyBWaXNpdW0gU3BhdGlhbCBHZW5lIEV4cHJlc3Npb24gU29sdXRpb24gYW5kIHRoZSBbU3BhY2VyYW5nZXIgcGlwZWxpbmVdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc3BhdGlhbC1nZW5lLWV4cHJlc3Npb24vc29mdHdhcmUvcGlwZWxpbmVzL2xhdGVzdC93aGF0LWlzLXNwYWNlLXJhbmdlcikuIEFsbCBkYXRhc2V0cyB0aGF0IHdlIHdpbGwgaW52ZXN0aWdhdGUgdG8gZGF5IGFyZSBhbGwgZnJlZWx5IGF2YWlsYWJsZSBmcm9tIFsxMHggR2Vub21pY3NdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc3BhdGlhbC1nZW5lLWV4cHJlc3Npb24vZGF0YXNldHMpLgoKUGxlYXNlIG5vdGUgdGhhdCB0aGlzIHR1dG9yaWFsIGlzIGxhcmdlbHkgYW4gZXh0ZW5zaW9uIG9mZiBvZiB0aGUgcHJpbWFyeSBbU2V1cmF0IFZpc2l1bSBUdXRvcmlhbF0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC92My4xL3NwYXRpYWxfdmlnbmV0dGUuaHRtbCkKCioqVGhpbmdzIHRvIGtub3cgYWJvdXQgdGhpcyB3b3Jrc2hvcCoqCgoxLiBBbGwgZmlsZXMgdGhhdCB3aWxsIGJlIHVzZWQgY2FuIGJlIGZvdW5kIGF0OiBgL21udC9saWJzL3NoYXJlZF9kYXRhL2AKClshW1Rlcm1pbmFsXShodHRwczovL2dpdGh1Yi5jb20vc3RlcGhlbndpbGxpYW1zMjIvU0lCXzIwMjBfV29ya3Nob3AvcmF3L21hc3Rlci9pbWFnZXMvVGVybWluYWwucG5nKV0KCjIuIEdldHRpbmcgc3RhcnRlZCB3aXRoIFIgYW5kIFZpc2l1bSBkYXRhIG91dHNpZGUgb2YgU2V1cmF0IGF0OiBodHRwczovL3N1cHBvcnQuMTB4Z2Vub21pY3MuY29tL3NwYXRpYWwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3QvcmtpdAozLiBSZWZlcmVuY2UgZ2Vub21lIGZvciBhbGwgc2FtcGxlcyBpcyBHUkNoMzgvbW0xMAo0LiBBbGwgMTB4IHNvZnR3YXJlIGluY2x1ZGluZyBbU3BhY2VyYW5nZXJdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc3BhdGlhbC1nZW5lLWV4cHJlc3Npb24vc29mdHdhcmUvcGlwZWxpbmVzL2xhdGVzdC93aGF0LWlzLXNwYWNlLXJhbmdlciksIGFuZCBbTG91cGUgQnJvd3Nlcl0oaHR0cHM6Ly9zdXBwb3J0LjEweGdlbm9taWNzLmNvbS9zcGF0aWFsLWdlbmUtZXhwcmVzc2lvbi9zb2Z0d2FyZS92aXN1YWxpemF0aW9uL2xhdGVzdC93aGF0LWlzLWxvdXBlLWJyb3dzZXIpLCBjYW4gYmUgZG93bmxvYWRlZCBmcm9tIHRoZSBbMTB4IFN1cHBvcnQgU2l0ZV0oaHR0cHM6Ly9zdXBwb3J0LjEweGdlbm9taWNzLmNvbS8pCjUuIE1vc3QgU2V1cmF0IGZpZ3VyZXMgY2FuIGJlIGNvbnRyb2xsZWQgd2l0aCBwYXRjaHdvcmsgYmVjYXVzZSB0aGV5IGFyZSBmdW5kYW1lbnRhbGx5IGdncGxvdCBvYmplY3RzLiBTb21lIHJlYWxseSBuaWNlIGV4YW1wbGVzIGNhbiBiZSBmb3VuZCBvbiB0aGUgW1BhdGNod29yayBHaXRIdWJdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS9hcnRpY2xlcy9ndWlkZXMvbGF5b3V0Lmh0bWwpCjYuIFRoaXMgdHV0b3JpYWwgaXMgX05PVF8gb2ZmaWNpYWxseSBzdXBwb3J0ZWQgYnkgMTB4IGdlbm9taWNzLiBJdCBpcyBzaW1wbHkgZm9yIGluZm9ybWF0aXZlIHB1cnBvc2VzIGFuZCB0byBwcm92aWRlIHRoZSBncm91bmR3b3JrIGZvciBpbmRpdmlkdWFscyB0byBnZXQgc3RhcnRlZCB3aXRoIDNyZCBwYXJ0eSBhbmFseXNpcyB0b29scy4KCgojICoqRXhwbG9yaW5nIFZpc2l1bSBEYXRhIHdpdGggU2V1cmF0KioKCiMjIExvYWQgb3VyIHBhY2thZ2VzCgpGaXJzdCB3ZSdsbCBzZXQgdGhlIF9saWJQYXRoXyB0byBtYWtlIHN1cmUgd2UgYXJlIGFsbCB1c2luZyB0aGUgc2FtZSBzZXQgb2YgcHJlLWluc3RhbGxlZCBsaWJyYXJpZXMuCmZvciAxMHgKYGBge3J9Ci5saWJQYXRocyhuZXcgPSBjKCIvbW50L2hvbWUvc3RlcGhlbi53aWxsaWFtcy9SL3g4Nl82NC1jb25kYV9jb3M2LWxpbnV4LWdudS1saWJyYXJ5LzMuNiIsCiAgICAgICAgICAgICAgICAgICIvbW50L29wdC9SL1ItMy42LjEtY29uZGEtb3BlbmJsYXMvUi1saWJyYXJ5IiAsICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICIvbW50L29wdC9SL1ItMy42LjEtY29uZGEtb3BlbmJsYXMvZW52L2xpYi9SL2xpYnJhcnkiKSkKYGBgCgoKZm9yIHR1dG9yaWFsCmBgYHtyLCBldmFsPUZBTFNFfQoubGliUGF0aHMobmV3ID0gIi91c3IvbGliNjQvUi9saWJyYXJ5IikKYGBgCgpgYGB7ciBMaWJyYXJpZXMsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCiMjIExvYWRpbmcgZGF0YSBpbiBhIFNldXJhdCBvYmplY3QKCk5vdyB3ZSdsbCBsb2FkIHVwIHRoZSBkYXRhc2V0IHRoYXQgd2lsbCBiZSB1c2VkIGZyb20gdGhpcyBwb2ludCBmb3J3YXJkIHVzaW5nIF9TZXVyYXQ6OkxvYWQxMFhfU3BhdGlhbF8gZnVuY3Rpb24uCgpSZWFsIERhdGFzZXQgZm9yIHRoZSB0dXRvcmlhbApgYGB7ciBldmFsPUZBTFNFfQpicmVhc3RfY2FuY2VyIDwtIExvYWQxMFhfU3BhdGlhbChkYXRhLmRpciA9ICIvbW50L2xpYnMvc2hhcmVkX2RhdGEvaHVtYW5fYnJlYXN0X2NhbmNlcl8xL291dHMvIiwKICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gIlYxX0JyZWFzdF9DYW5jZXJfQmxvY2tfQV9TZWN0aW9uXzFfZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXguaDUiKQpgYGAKClNhbWUgZGF0YSBqdXN0IGludGVybmFsIHRvIDEweApgYGB7cn0KYnJlYXN0X2NhbmNlciA8LSBMb2FkMTBYX1NwYXRpYWwoZGF0YS5kaXIgPSAiL21udC9hbmFseXNpcy9tYXJzb2MvcGlwZXN0YW5jZXMvSFdIVEZEU1hYL1NQQVRJQUxfUk5BX0NPVU5URVJfUEQvMTYzMDg2L0hFQUQvb3V0cy8iLCBzbGljZSA9ICJzbGljZTEiKQpgYGAKCk5vdGUgdGhhdCB0aGUgRGVmYXVsdCBBc3NheSBpcyBzZXQgdG8gIlNwYXRpYWwiCmBgYHtyfQpEZWZhdWx0QXNzYXkoYnJlYXN0X2NhbmNlcikKYGBgCkl0J3MgZ29vZCB0byBub3RlIHRoYXQgdGhlcmUgYXJlIGEgYnVuY2ggb2YgVmlzaXVtIGRhdGEgc2V0cyBob3N0ZWQgYnkgdGhlIFNhdGlqYSBsYWIgaW4gdGhlIFtTZXVyYXQgRGF0YSBQYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vc2F0aWphbGFiL3NldXJhdC1kYXRhKS4KCiMgKipSZXN1bHRzKioKIyMgUUMKCkl0J3MgdmVyeSBlYXN5IHRvIGFkZCBtZXRhZGF0YSB0byB5b3VyIFNldXJhdCBvYmplY3Qgd2l0aCBhbnkgdmFsdWVzIHlvdSB3YW50IHRvIGNoZWNrIG91dCBvbiB0b3Agb2YgdGhlIGRlZmF1bHRzLgoKYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTgsIHdhcm5pbmc9RkFMU0V9Cm1pdG8uZ2VuZS5uYW1lcyA8LSBncmVwKCJebXQtIiwgcm93bmFtZXMoYnJlYXN0X2NhbmNlckBhc3NheXMkU3BhdGlhbCksIHZhbHVlID0gVFJVRSwgaWdub3JlLmNhc2UgPSBUUlVFKQpjb2wudG90YWwgPC0gTWF0cml4Ojpjb2xTdW1zKGJyZWFzdF9jYW5jZXJAYXNzYXlzJFNwYXRpYWwpCmJyZWFzdF9jYW5jZXIgPC0gQWRkTWV0YURhdGEoYnJlYXN0X2NhbmNlciwgTWF0cml4Ojpjb2xTdW1zKGJyZWFzdF9jYW5jZXJAYXNzYXlzJFNwYXRpYWxbbWl0by5nZW5lLm5hbWVzLCBdKSAvIGNvbC50b3RhbCwgInBjdC5taXRvIikKYGBgCgoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgc29tZSBiYXNpYyBRQyBpbmZvcm1hdGlvbi4gS2VlcCBpbiBtaW5kIHRoYXQgbW9zdCBTZXVyYXQgcGxvdHMgYXJlIGdncGxvdCBvYmplY3QgYW5kIGNhbiBiZSBtYW5pcHVsYXRlZCBhcyBzdWNoLgoKKipDb3VudHMgPSBVTUkqKgoKKipGZWF0dXJlcyA9IEdlbmVzKioKYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTQsIHdhcm5pbmc9RkFMU0V9CnBsb3QxIDwtIFZsblBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkNvdW50X1NwYXRpYWwiLCBwdC5zaXplID0gMC4xKSArIAogIGdndGl0bGUoIlVNSSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgTm9MZWdlbmQoKQoKcGxvdDIgPC0gVmxuUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuRmVhdHVyZV9TcGF0aWFsIiwgcHQuc2l6ZSA9IDAuMSkgKyAKICBnZ3RpdGxlKCJHZW5lcyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgTm9MZWdlbmQoKQoKcGxvdDMgPC0gVmxuUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJwY3QubWl0byIsIHB0LnNpemUgPSAwLjEpICsgCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBNaXRvIGdlbmVzIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsKICBOb0xlZ2VuZCgpCnBsb3Q0IDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuQ291bnRfU3BhdGlhbCIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCnBsb3Q1IDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuRmVhdHVyZV9TcGF0aWFsIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCnBsb3Q2IDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJwY3QubWl0byIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQoKcGxvdDEgKyBwbG90MiArIHBsb3QzICArIHBsb3Q0ICsgcGxvdDUgKyBwbG90NiArIHBsb3RfbGF5b3V0KG5yb3cgPSAyLCBuY29sID0gMykKYGBgCgojIyBOb3JtYWxpemF0aW9uCgpTcGFjZXJhbmdlciBkb2VzIFVNSSBub3JtYWxpemF0aW9uIGZvciBjbHVzdGVyaW5nIGFuZCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBidXQgZG9lcyBub3QgcmV0dXJuIHRoYXQgbm9ybWFsaXplZCBtYXRyaXguCgpMZXQncyBoYXZlIGEgbG9vayBhdCBwcmUtbm9ybWFsaXphdGlvbiByYXcgVU1JIGNvdW50cy4gRmVlbCBmcmVlIHRvIGNoYW5nZSB0aGVzZSBnZW5lcyBvciBhZGQgZ2VuZXMuCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IGMoIkVSQkIyIiwgIkNEOEEiLCAiTVQtTkQxIikpCmBgYAoKIyMjIFNFIHRyYW5zZm9ybQoKKyAgVGhpcyB3aWxsIHRha2UgfjMtNCBtaW4uIAoKRG9uJ3Qgd29ycnkgYWJvdXQgYHJlYWNoZWRpdGVyYXRpb24gbGltaXRgIHdhcm5pbmdzLiBTZWUgaHR0cHM6Ly9naXRodWIuY29tL0NocmlzdG9waEgvc2N0cmFuc2Zvcm0vaXNzdWVzLzI1IGZvciBkaXNjdXNzaW9uCgpEZWZhdWx0IGFzc2F5IHdpbGwgbm93IGJlIHNldCB0byBTQ1QKYGBge3Igd2FybmluZz1GQUxTRX0KYnJlYXN0X2NhbmNlciA8LSBTQ1RyYW5zZm9ybShicmVhc3RfY2FuY2VyLCBhc3NheSA9ICJTcGF0aWFsIiwgdmVyYm9zZSA9IEZBTFNFKQpgYGAKCk5vdyBsZXQncyBoYXZlIGEgbG9vayBhdCBTQ1Qgbm9ybWFsaXplZCBVTUkgY291bnRzIGZvciB0aGVzZSBzYW1lIGdlbmVzLiBUaGUgRGVmYXVsdCBBc3NheXMgaXMgbm93ICJTQ1QiCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IGMoIkVSQkIyIiwgIkNEOEEiKSkKYGBgCgpGcm9tIFNldXJhdDogCgpUaGUgZGVmYXVsdCBwYXJhbWV0ZXJzIGluIFNldXJhdCBlbXBoYXNpemUgdGhlIHZpc3VhbGl6YXRpb24gb2YgbW9sZWN1bGFyIGRhdGEuIEhvd2V2ZXIsIHlvdSBjYW4gYWxzbyBhZGp1c3QgdGhlIHNpemUgb2YgdGhlIHNwb3RzIChhbmQgdGhlaXIgdHJhbnNwYXJlbmN5KSB0byBpbXByb3ZlIHRoZSB2aXN1YWxpemF0aW9uIG9mIHRoZSBoaXN0b2xvZ3kgaW1hZ2UsIGJ5IGNoYW5naW5nIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVyczoKCisgIHB0LnNpemUuZmFjdG9yLSBUaGlzIHdpbGwgc2NhbGUgdGhlIHNpemUgb2YgdGhlIHNwb3RzLiBEZWZhdWx0IGlzIDEuNgorICBhbHBoYSAtIG1pbmltdW0gYW5kIG1heGltdW0gdHJhbnNwYXJlbmN5LiBEZWZhdWx0IGlzIGMoMSwgMSkuCisgIFRyeSBzZXR0aW5nIHRvIGFscGhhIGMoMC4xLCAxKSwgdG8gZG93bndlaWdodCB0aGUgdHJhbnNwYXJlbmN5IG9mIHBvaW50cyB3aXRoIGxvd2VyIGV4cHJlc3Npb24KCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9CnAxIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiLCBwdC5zaXplLmZhY3RvciA9IDEpKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgZ2d0aXRsZSgiQWN0dWFsIFNwb3QgU2l6ZSIpCnAyIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiKSsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIGdndGl0bGUoIlNjYWxlZCBTcG90IFNpemUiKQpwMSArIHAyCmBgYAoKIyMjIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgY2x1c3RlcmluZywgYW5kIHZpc3VhbGl6YXRpb24KCldlIGNhbiB0aGVuIHByb2NlZWQgdG8gcnVuIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgY2x1c3RlcmluZyBvbiB0aGUgUk5BIGV4cHJlc3Npb24gZGF0YSwgdXNpbmcgdGhlIHNhbWUgd29ya2Zsb3cgYXMgd2UgdXNlIGZvciBzY1JOQS1zZXEgYW5hbHlzaXMuCgpTb21lIG9mIHRoZXNlIHByb2Nlc3NlcyBjYW4gYmUgcGFyYWxsaXplZCBwbGVhc2Ugc2VlIFtQYXJhbGxlbGl6YXRpb24gaW4gU2V1cmF0XShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3YzLjEvZnV0dXJlX3ZpZ25ldHRlLmh0bWwpIGZvciBtb3JlIGluZm8KClRoZSBkZWZhdWx0IFVNQVAgY2FsY3VsYXRpb24gaXMgcGVyZm9ybWVkIHdpdGggdGhlIFtSLWJhc2VkIFVXT1RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy91d290L2luZGV4Lmh0bWwpIGxpYnJhcnkgSG93ZXZlciwgeW91IGNhbiBydW4gVU1BUCBpbiBweXRob24gdmlhIHRoZSByZXRpY3VsYXRlIGxpYnJhcnkgYW5kIGB1bWFwLWxlYXJuYC4gV2UgaGF2ZSBmb3VuZCB0aGF0IGZvciBzbWFsbGVyIGRhdGEgc2V0cyAoPD0gMTBrIGNlbGxzL3Nwb3RzKSBVV09UIGlzIGdyZWF0LiBGb3IgbXVjaCBsYXJnZXIgZGF0YSBzZXRzICgxMDBrICsgY2VsbHMvc3BvdHMpIGB1bWFwLWxlYXJuYCBjYW4gYmUgYSBmYXN0ZXIgb3B0aW9uLgoKIyMjIyBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24KCkZpcnN0IExldCdzIFJ1biBvdXIgUENBLiBIb3cgbWFueSBQQ3Mgc2hvdWxkIHdlIHVzZSBnb2luZyBmb3J3YXJkPwpgYGB7cn0KYnJlYXN0X2NhbmNlciA8LSBSdW5QQ0EoYnJlYXN0X2NhbmNlciwgYXNzYXkgPSAiU0NUIiwgdmVyYm9zZSA9IEZBTFNFKQpicmVhc3RfY2FuY2VyIDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIpCkVsYm93UGxvdChicmVhc3RfY2FuY2VyLCBuZGltcyA9IDQwKQpgYGAKIyMjIyBDbHVzdGVyaW5nCgpOb3cgbGV0J3MgY2x1c3RlciBhbmQgcHJvamVjdCB0byBVTUFQLiBEb2VzIDMwIFBDcyBsb29rIG9rYXk/IFdoYXQgaWYgd2UgY2hhbmdlZCB0aGUgbnVtYmVyIG9mIGRpbWVuc2lvbnMgdG8gMjA/CmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIEZpbmROZWlnaGJvcnMoYnJlYXN0X2NhbmNlciwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjMwKQpicmVhc3RfY2FuY2VyIDwtIEZpbmRDbHVzdGVycyhicmVhc3RfY2FuY2VyLCB2ZXJib3NlID0gRkFMU0UpCmBgYAoKCmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIFJ1blVNQVAoYnJlYXN0X2NhbmNlciwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjMwKQpgYGAKCk5vdyBsZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgY2x1c3RlcmluZyBpbiBVTUFQIHNwYWNlCgpgYGB7ciwgZmlnLndpZHRoPTEwfQpwMSA8LSBEaW1QbG90KGJyZWFzdF9jYW5jZXIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBGQUxTRSkgKwogIGxhYnMoY29sb3IgPSAiQ2x1c3RlciIpCgpwMiA8LSBGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IGMoJ25GZWF0dXJlX1NwYXRpYWwnLCdwY3QubWl0bycpLCBkaW1zID0gMToyKQoKcDEgLyBwMiAKYGBgCgpIZXJlJ3MgdGhlIGNsdXN0ZXJpbmcgaW4gVU1BUCBhbmQgaW1hZ2Ugc3BhY2UKYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBEaW1QbG90KGJyZWFzdF9jYW5jZXIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFKSArCiAgbGFicyhjb2xvciA9ICJDbHVzdGVyIikKcDIgPC0gU3BhdGlhbERpbVBsb3QoYnJlYXN0X2NhbmNlciwgbGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMykgKwogIGxhYnMoZmlsbCA9ICJDbHVzdGVyIikKCnAxICsgcDIgKyBwbG90X2Fubm90YXRpb24oCiAgdGl0bGUgPSAnQ2x1c3RlcmluZyBpbiBVTUFQIGFuZCBUaXNzdWUgU3BhY2UnLAogIGNhcHRpb24gPSAnUHJvY2Vzc2VkIGJ5IFNwYWNlcmFuZ2VyIDEuMVxuTm9ybWFsaXphdGlvbiBhbmQgQ2x1c3RlcmluZyBieSBTZXVyYXQnCikgKyBwbG90X2xheW91dChucm93ID0gMSkKYGBgCgpJIGRvbid0IHJlYWxseSBsaWtlIHRoZXNlIGNvbG9ycyBzbyBsZXQncyBjaGFuZ2UgdGhlbSBtYW51YWxseQoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMCwgd2FybmluZz1GQUxTRX0KcDEgPC0gRGltUGxvdChicmVhc3RfY2FuY2VyLCByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gVFJVRSkgKwogIGxhYnMoY29sb3IgPSAiQ2x1c3RlciIpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiNiMmRmOGEiLCIjZTQxYTFjIiwiIzM3N2ViOCIsIiM0ZGFmNGEiLCIjZmY3ZjAwIiwiZ29sZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNhNjU2MjgiLCAiIzk5OTk5OSIsICJibGFjayIsICJwaW5rIiwgInB1cnBsZSIsICJicm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ3JleSIsICJ5ZWxsb3ciLCAiZ3JlZW4iKSkKCnAyIDwtIFNwYXRpYWxEaW1QbG90KGJyZWFzdF9jYW5jZXIsIGxhYmVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsKICBsYWJzKGZpbGwgPSAiQ2x1c3RlciIpKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIikpCgpwMSArIHAyICsgcGxvdF9hbm5vdGF0aW9uKAogIHRpdGxlID0gJ0NsdXN0ZXJpbmcgaW4gVU1BUCBhbmQgVGlzc3VlIFNwYWNlJywKICBjYXB0aW9uID0gJ1Byb2Nlc3NlZCBieSBTcGFjZXJhbmdlciAxLjFcbk5vcm1hbGl6YXRpb24gYW5kIENsdXN0ZXJpbmcgYnkgU2V1cmF0JwopICsgcGxvdF9sYXlvdXQobnJvdyA9IDEpCmBgYAoKSWYgaW50ZXJlc3RlZCB5b3UgY2FuIGFsc28gbm93IGxvb2sgYXQgVU1JIGFuZCBHZW5lIGNvdW50cyBwZXIgY2x1c3RlciBhcyB3ZWxsCmBgYHtyfQpwbG90MSA8LSBWbG5QbG90KGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIm5Db3VudF9TcGF0aWFsIiwgcHQuc2l6ZSA9IDAuMSkgKyAKICBnZ3RpdGxlKCJVTUkiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2IyZGY4YSIsIiNlNDFhMWMiLCIjMzc3ZWI4IiwiIzRkYWY0YSIsIiNmZjdmMDAiLCJnb2xkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiI2E2NTYyOCIsICIjOTk5OTk5IiwgImJsYWNrIiwgInBpbmsiLCAicHVycGxlIiwgImJyb3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmV5IiwgInllbGxvdyIsICJncmVlbiIpKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgTm9MZWdlbmQoKQoKcGxvdDIgPC0gVmxuUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJuRmVhdHVyZV9TcGF0aWFsIiwgcHQuc2l6ZSA9IDAuMSkgKyAKICBnZ3RpdGxlKCJHZW5lcyIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIikpKwogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIE5vTGVnZW5kKCkKCnBsb3QxICsgcGxvdDIKYGBgCgpXZSBjYW4gYWxzbyBsb29rIGF0IHNvbWUgb2Ygb3VyIFFDIGluZm9ybWF0aW9uIGJ5IGNsdXN0ZXIgbm93IHRoYXQgd2UndmUgcHJvY2Vzc2VkIHRoZSBkYXRhCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD00LCB3YXJuaW5nPUZBTFNFfQpwbG90MSA8LSBWbG5QbG90KGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIm5Db3VudF9TcGF0aWFsIiwgcHQuc2l6ZSA9IDAuMSkgKyAKICBnZ3RpdGxlKCJVTUkiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIE5vTGVnZW5kKCkKCnBsb3QyIDwtIFZsblBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkZlYXR1cmVfU3BhdGlhbCIsIHB0LnNpemUgPSAwLjEpICsgCiAgZ2d0aXRsZSgiR2VuZXMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogIE5vTGVnZW5kKCkKCnBsb3QzIDwtIFZsblBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAicGN0Lm1pdG8iLCBwdC5zaXplID0gMC4xKSArIAogIGdndGl0bGUoIlBlcmNlbnRhZ2UgTWl0byBnZW5lcyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArCiAgTm9MZWdlbmQoKQpwbG90NCA8LSBTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkNvdW50X1NwYXRpYWwiKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCgpwbG90NSA8LSBTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAibkZlYXR1cmVfU3BhdGlhbCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpwbG90NiA8LSBTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSAicGN0Lm1pdG8iKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCnBsb3QxICsgcGxvdDIgKyBwbG90MyAgKyBwbG90NCArIHBsb3Q1ICsgcGxvdDYgKyBwbG90X2xheW91dChucm93ID0gMiwgbmNvbCA9IDMpCmBgYAoKTm93IGxldCdzIHRha2UgYSBsb29rIGF0IGF0IGEgZ2VuZSBvZiBpbnRlcmVzdCB3aXRoIHZpb2xpbiBwbG90cyBidXQgYWxzbyBpbiBpbWFnZSBzcGFjZS4gVGhlIHRyaWFuZ2xlcyByZXByZXNlbnQgdGhlIG1lYW4gZXhwcmVzc2lvbiBvZiBlYWNoIGNsdXN0ZXIuCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OCwgd2FybmluZz1GQUxTRX0KcDEgPC0gVmxuUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiLCBwdC5zaXplID0gMC4xKSArIAogICAgICAgICAgICAgIGdndGl0bGUoIklHRkJQNSIpICsKICAgICAgICAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIikpKwogICAgICAgICAgICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKwogICAgICAgICAgICAgIE5vTGVnZW5kKCkgKwogICAgICAgICAgICAgIHN0YXRfc3VtbWFyeShmdW49bWVhbiwgZ2VvbT0icG9pbnQiLCBzaGFwZT0yMywgc2l6ZT00LCBjb2xvcj0icmVkIikKCnAyIDwtIFNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9ICJJR0ZCUDUiKSsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCnAzIDwtIFNwYXRpYWxEaW1QbG90KGJyZWFzdF9jYW5jZXIsIGxhYmVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsKICBsYWJzKGZpbGwgPSAiQ2x1c3RlciIpKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIikpICsKICBOb0xlZ2VuZCgpCgpyb3cxIDwtIHAyICsgcDMgKyBwbG90X2xheW91dChucm93ID0gMSkKCnJvdzEgKyBwMSsgcGxvdF9sYXlvdXQobnJvdyA9IDIsIHdpZHRocyA9IGMoMC41LCAwLjUpKQpgYGAKCldlIGNhbiBhbHNvIGxvb2sgYXQgdGhlc2UgZGF0YSBpbnRlcmFjdGl2ZWx5LiBUaGlzIGZ1bmN0aW9uIGNhbiBiZSBhIGxpdHRsZSBzbG93IGJ1dCBhbHNvIHZlcnkgdXNlZnVsIHRvIHZpc3VhbGl6ZSBleHByZXNzaW9uIGluIGRpZmZlcmVudCBwcm9qZWN0aW9uIHNwYWNlcy4gV2Ugd29uJ3QgcnVuIHRoaXMgdG9kYXkKCmBgYApMaW5rZWREaW1QbG90KGJyZWFzdF9jYW5jZXIpCmBgYAoKIyMgU3BhdGlhbGx5IHZhcmlhYmxlIGZlYXR1cmVzCgpGaXJzdCB3ZSdsbCBpZGVudGlmeSBkZWZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcy4gTGV0J3MgZmluZCBhbGwgdGhlIG1hcmtlcnMgZm9yIGV2ZXJ5IGNsdXN0ZXIuIFdlJ3ZlIGFscmVhZHkgcHJlIGNhbGN1bGF0ZWQgdGhlc2UgZm9yIHlvdSBzbyBsZXQncyBqdXN0IGxvYWQgdGhlbSB1cC4gCndvcmtzaG9wCmBgYHtyLCBldmFsPUZBTFNFfQpkZV9tYXJrZXJzIDwtIHJlYWRSRFMoZmlsZSA9ICIvbW50L2xpYnMvc2hhcmVkX2RhdGEvZGVfbWFya2Vycy5yZHMiKQoKZGVfbWFya2VycyAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgdG9wX24obiA9IDIsIHd0ID0gYXZnX2xvZ0ZDKQpgYGAKCgoKMTB4CmBgYHtyfQpkZV9tYXJrZXJzIDwtIHJlYWRSRFMoZmlsZSA9ICIvbW50L2hvbWUvc3RlcGhlbi53aWxsaWFtcy95YXJkL09kaW4vU0lCXzIwMjBfV29ya3Nob3AvZGVfbWFya2Vycy5yZHMiKQoKZGVfbWFya2VycyAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgdG9wX24obiA9IDIsIHd0ID0gYXZnX2xvZ0ZDKQpgYGAKCk9yaWdpbmFsbHkgdGhpcyB3YXMgcHJvY2Vzc2VkIHdpdGggCmBgYApkZV9tYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKGJyZWFzdF9jYW5jZXIsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMjUsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUpCmBgYAoKIyMjIElkZW50aWZ5IHRoZSBtb3N0IHVwLXJlZ3VsYXRlZCBhbmQgZG93bi1yZWd1bGF0ZWQgZ2VuZXMKYGBge3J9CmRlX21hcmtlcnNfdXAgPC0gZGVfbWFya2VycyAlPiUKICBhcnJhbmdlKC1hdmdfbG9nRkMpCgpkZV9tYXJrZXJzX2Rvd24gPC0gZGVfbWFya2VycyAlPiUKICBhcnJhbmdlKGF2Z19sb2dGQykKYGBgCgpgYGB7cn0KZGVfbWFya2Vyc191cApgYGAKCmBgYHtyfQpkZV9tYXJrZXJzX2Rvd24KYGBgCgojIyMjIE1vc3QgdXAtcmVndWxhdGVkIGdlbmVzCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTAsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChvYmplY3QgPSBicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IGRlX21hcmtlcnNfdXAkZ2VuZVsxOjEzXSwgYWxwaGEgPSBjKDAuMSwgMSksIG5jb2wgPSAzKQpgYGAKCiMjIyMgTW9zdCBkb3duLXJlZ3VsYXRlZCBnZW5lcwpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwLCB3YXJuaW5nPUZBTFNFfQpTcGF0aWFsRmVhdHVyZVBsb3Qob2JqZWN0ID0gYnJlYXN0X2NhbmNlciwgZmVhdHVyZXMgPSBkZV9tYXJrZXJzX2Rvd24kZ2VuZVsxOjEzXSwgYWxwaGEgPSBjKDAuMSwgMSksIG5jb2wgPSAzKQpgYGAKCldoYXQgYXJlIHRoZSB0b3AgdmFyaWFibGUgZmVhdHVyZXM/CmBgYHtyfQpWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIpWzE6MTBdCmBgYAoKV2hhdCBhcmUgdGhlIHRvcCBERSBnZW5lcz8KYGBge3J9CnJvd25hbWVzKGRlX21hcmtlcnMpWzE6MTBdCmBgYAoKIyMgU3BhdGlhbGx5IFZhcmlhYmxlIEdlbmVzCgpTbyB3aGF0IGFib3V0IHNwYXRpYWwgZW5yaWNobWVudD8gVGhpcyBjYW4gYmUgYSB2ZXJ5IGluZm9ybWF0aXZlIGFuYWx5c2lzIHRvb2wgdGhhdCB0YWtlcyBpbnRvIHRoZSBzcGF0aWFsIHJlbGF0aW9uc2hpcCBvZiBlYWNoIGdlbmUuCgpTb21lIG1ldGhvZHMgZm9yIHRoZXNlIGFwcHJvYWNoZXMgYXJlOgoKMS4gW1RyZW5kc2NlZWtdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguNDYzNCkKMi4gW1NwbG90Y2hdKGh0dHBzOi8vd3d3LmJpb3J4aXYub3JnL2NvbnRlbnQvMTAuMTEwMS83NTcwOTZ2MSkKMy4gW1NQQVJLXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5Mi0wMTktMDcwMS03KQo0LiBbU3BhdGlhbERFXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL25tZXRoLjQ2MzYpCiAgKyBXZSBoYXZlIGZvdW5kIHRoaXMgaW1wbGVtZW50YXRpb24gbm90IHRvIGJlIHZlcnkgZWZmZWN0aXZlLiBJdCdzIGFsc28gbm90IHVuZGVyIGFjdGl2ZSBkZXZlbG9wbWVudAoKClVzaW5nIHRoZSB0b3AgMTAwIHZhcmlhYmxlIGdlbmVzIGZpbmQgc3BhdGlhbGx5IGVucmljaGVkIG9uZXMuIE5vdGUgdGhhdCBpbiB0aGUgU2V1cmF0IFNwYXRpYWwgVHV0b3JpYWwgdGhleSB1c2UgMTAwMCBnZW5lcyAodGhpcyBjYW4gdGFrZSBhIGxvbmcgdGltZSkuIFlvdSBjYW4gYWxzbyB1c2UgYWxsIGdlbmVzIGJ1dCB0aGF0IHdpbGwgdGFrZSBhIGxvbmcgdGltZS4gVXNpbmcgYSBjYWxjdWxhdGlvbiBvZiBbTW9yYW5zIEldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01vcmFuJTI3c19JKSBjYW4gc29tZXRpbWVzIGJlIGEgZmFzdGVyIGFwcHJvYWNoLCBlc3BlY2lhbGx5IGlmIHlvdSBhcmUgdXNpbmcgcGFyYWxsaXphdGlvbi4gSGVyZSB3ZSdsbCBkbyBib3RoLgoKV2hpbGUgdGhpcyBwcm9jZXNzIGlzIHJ1bm5pbmcgaXQgaXMgYSBnb29kIHRpbWUgdG8gdGFrZSBhIHNob3J0IGNvdXBsZSBtaW51dGUgYnJlYWssIGNhdGNoIHVwLCBvciBhc2sgcXVlc3Rpb25zLgoKCmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIEZpbmRTcGF0aWFsbHlWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyKVsxOjEwMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0aW9uLm1ldGhvZCA9ICJtYXJrdmFyaW9ncmFtIiwgdmVyYm9zZSA9IFRSVUUpCmBgYAoKSGF2ZSBhIGxvb2sgYXQgdGhlIHNwYXRpYWxseSB2YXJpYWJsZSBnZW5lcyBjYWxjdWxhdGVkIGJ5IGBtYXJrdmFyaW9ncmFtYCBvcmRlcmVkIGZyb20gbW9zdCB2YXJpYWJsZSB0byBsZWFzdCB2YXJpYWJsZQpgYGB7cn0KU3BhdGlhbGx5VmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyLCBzZWxlY3Rpb24ubWV0aG9kID0gIm1hcmt2YXJpb2dyYW0iLCBkZWNyZWFzaW5nID0gVFJVRSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTgsIHdhcm5pbmc9RkFMU0V9CnRvcC5mZWF0dXJlc190cmVuZHNlcSA8LSBoZWFkKFNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtYXJrdmFyaW9ncmFtIiksIDgpClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IHRvcC5mZWF0dXJlc190cmVuZHNlcSwgbmNvbCA9IDQsIGFscGhhID0gYygwLjEsIDEpKQpgYGAKCk1vcmFuJ3MgSSBpbXBsZW1lbnRhdGlvbi4gRm9yIG90aGVyIHNwYXRpYWwgZGF0YSB0eXBlcyB0aGUgeC5jdXRzIGFuZCB5LmN1dHMgZGV0ZXJtaW5lcyB0aGUgZ3JpZCB0aGF0IGlzIGxhaWQgb3ZlciB0aGUgdGlzc3VlIGluIHRoZSBjYXB0dXJlIGFyZWEuIEhlcmUgd2UnbGwgcmVtb3ZlIHRob3NlCmBgYHtyfQpicmVhc3RfY2FuY2VyIDwtIEZpbmRTcGF0aWFsbHlWYXJpYWJsZUZlYXR1cmVzKGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhicmVhc3RfY2FuY2VyKVsxOjEwMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIikKYGBgCgoKSGF2ZSBhIGxvb2sgYXQgdGhlIHNwYXRpYWxseSB2YXJpYWJsZSBnZW5lcyBjYWxjdWxhdGVkIGJ5IGBtb3JhbnNpYCBvcmRlcmVkIGZyb20gbW9zdCB2YXJpYWJsZSB0byBsZWFzdCB2YXJpYWJsZQoKYGBge3J9ClNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIiwgZGVjcmVhc2luZyA9IFRSVUUpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04LCB3YXJuaW5nPUZBTFNFfQp0b3AuZmVhdHVyZXNfbW9yYW5zaSA8LSBoZWFkKFNwYXRpYWxseVZhcmlhYmxlRmVhdHVyZXMoYnJlYXN0X2NhbmNlciwgc2VsZWN0aW9uLm1ldGhvZCA9ICJtb3JhbnNpIiksIDgpClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCBmZWF0dXJlcyA9IHRvcC5mZWF0dXJlc19tb3JhbnNpLCBuY29sID0gNCwgYWxwaGEgPSBjKDAuMSwgMSkpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSByZXN1bHRzIGFyZSBzbGlnaHRseSBkaWZmZXJlbnQuIFNvIGxldCdzIHRha2UgYSBsb29rIGF0IHdoYXQgdGhvc2UgZGlmZmVyZW5jZSBhcmUKCmBgYHtyfQpzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMgPC0gYnJlYXN0X2NhbmNlckBhc3NheXMkU0NUQG1ldGEuZmVhdHVyZXMgJT4lCiAgdGlkeXI6OmRyb3BfbmEoKQoKc3BhdGlhbGx5X3ZhcmlhYmxlX2dlbmVzCmBgYApZb3UgY2FuIHNlZSB0aGUgdHdvIG1ldGhvZHMgc2hvdyAKYGBge3J9Cm1tX2NvciA8LSBjb3IudGVzdChzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMkbW9yYW5zaS5zcGF0aWFsbHkudmFyaWFibGUucmFuaywgc3BhdGlhbGx5X3ZhcmlhYmxlX2dlbmVzJG1hcmt2YXJpb2dyYW0uc3BhdGlhbGx5LnZhcmlhYmxlLnJhbmspCmdncGxvdChzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMsIGFlcyh4PW1vcmFuc2kuc3BhdGlhbGx5LnZhcmlhYmxlLnJhbmsseT1tYXJrdmFyaW9ncmFtLnNwYXRpYWxseS52YXJpYWJsZS5yYW5rKSkrCiAgZ2VvbV9wb2ludCgpKwogIGdlb21fc21vb3RoKCkrCiAgeGxhYigiTW9yYW5zIEkgUmFuayIpKwogIHlsYWIoIk1hcmt2YXJpb2dyYW0gUmFuayIpKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDI1LCB5ID0gNzUsIGxhYmVsID0gcGFzdGUoIlBlYXJzb24ncyBDb3JyZWxhdGlvblxuIiwgcm91bmQobW1fY29yJGVzdGltYXRlWzFdLCBkaWdpdHMgPSAyKSwgc2VwID0gIiIpKSsKICB0aGVtZV9idygpCmBgYAoKCldlIGNhbiBpZGVudGlmeSB0aGVzZSBvdXRsaWVycyBpbnRlcmFjdGl2ZWx5IHVzaW5nIGdncGxvdGx5CmBgYHtyLCB3YXJuaW5nfQpwbG90bHk6OmdncGxvdGx5KAogIGdncGxvdChzcGF0aWFsbHlfdmFyaWFibGVfZ2VuZXMsIGFlcyh4PW1vcmFuc2kuc3BhdGlhbGx5LnZhcmlhYmxlLnJhbmsseT1tYXJrdmFyaW9ncmFtLnNwYXRpYWxseS52YXJpYWJsZS5yYW5rLCBsYWJlbCA9cm93Lm5hbWVzKHNwYXRpYWxseV92YXJpYWJsZV9nZW5lcykpKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9zbW9vdGgoKSsKICB4bGFiKCJNb3JhbnMgSSBSYW5rIikrCiAgeWxhYigiTWFya3ZhcmlvZ3JhbSBSYW5rIikrCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMjUsIHkgPSA3NSwgbGFiZWwgPSBwYXN0ZSgiUGVhcnNvbidzIENvcnJlbGF0aW9uXG4iLCByb3VuZChtbV9jb3IkZXN0aW1hdGVbMV0sIGRpZ2l0cyA9IDIpLCBzZXAgPSAiIikpKwogIHRoZW1lX2J3KCkKKQpgYGAKCgojIyBDYW5jZXIgYW5ub3RhdGlvbnMKCldoZXJlIGFyZSB0aGVzZSBnZW5lcyBiZWluZyBleHByZXNzZWQgcmVsYXRpdmUgdG8gcGF0aG9sb2dpc3QgYW5ub3RhdGlvbj8KCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQpjYSA8LSByZWFkYml0bWFwOjpyZWFkLmJpdG1hcCgiL21udC9ob21lL3N0ZXBoZW4ud2lsbGlhbXMveWFyZC9PZGluL1NJQl8yMDIwX1dvcmtzaG9wL2ltYWdlcy9CcmVhc3QgQ2FuY2VyIFBhdGgucG5nIikKIyBpbiB0aGUgdHV0b3JpYWwKIyBjYSA8LSByZWFkYml0bWFwOjpyZWFkLmJpdG1hcCgnL21udC9saWJzL3NoYXJlZF9kYXRhL2h1bWFuX2JyZWFzdF9jYW5jZXJfMS9pbWFnZXMvQnJlYXN0X0NhbmNlcl9QYXRoLnBuZycpCnBsb3QoMDoxLDA6MSx0eXBlPSJuIixhbm49RkFMU0UsYXhlcz1GQUxTRSkKcmFzdGVySW1hZ2UoY2EsMCwwLDEsMSkKYGBgCgpMb29rcyBsaWtlIHRoZSAgTWF0cml4IEdsYSBwcm90ZWluICggX01HUF8gKSBnZW5lIGlzIGVucmljaGVkIGluIER1Y3RhbCBDYXJjaW5vbWEgX0luIFNpdHVfLiBOb3QgYSBsb3QgaXMga25vd24gYWJvdXQgX01HUF8gaW4gdGhlIGNvbnRleHQgb2YgY2FuY2VyIGJ1dCBpdCBsb29rcyBsaWtlIGl0IGNvdWxkIGJlIGFuIGludGVyZXN0aW5nIG5vdmVsIGdlbmUgdG8gaW52ZXN0aWdhdGUgd2l0aCByZWdhcmQgdG8gRHVjdGFsIENhcmNpbm9tYSBfSW4gU2l0dV8uCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KU3BhdGlhbEZlYXR1cmVQbG90KG9iamVjdCA9IGJyZWFzdF9jYW5jZXIsIGZlYXR1cmVzID0gIk1HUCIsIGFscGhhID0gYygwLjEsIDEpLCBuY29sID0gMykKYGBgCgojICoqU2luZ2xlIGNlbGwvbnVjbGVpIGludGVncmF0aW9uKioKCkhlcmUgd2UgaGF2ZSBhIHByZXByb2Nlc3NlZCBTZXVyYXQgb2JqZWN0IHdpdGggMTBrIG51Y2xlaSBhbm5vdGF0ZWQgZnJvbSBhIGJyZWFzdCBjYW5jZXIgc2FtcGxlLiBEb24ndCBib3RoZXIgdG9vIG11Y2ggd2l0aCB0aGUgZGV0YWlscyBvZiBob3cgdGhpcyBkYXRhIHdhcyBnZW5lcmF0ZWQgdGhleSBkb24ndCBwYXJ0aWN1bGFybHkgbWF0dGVyIGZvciBvdXIgcHVycG9zZXMuIAoKIyMgTG9hZCB0aGUgU2V1cmF0IG9iamVjdAoKMTB4IGluZnJhc3RydWN0dXJlCmBgYHtyfQpiY19zblJOQSA8LSByZWFkUkRTKCIvbW50L2hvbWUvc3RlcGhlbi53aWxsaWFtcy95YXJkL09kaW4vU0lCXzIwMjBfV29ya3Nob3AvYmNfc25STkEucmRzIikKYGBgCgpGb3IgdGhlIHdvcmtzaG9wCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KYmNfc25STkEgPC0gcmVhZFJEUygiL21udC9saWJzL3NoYXJlZF9kYXRhL2JjX3NuUk5BLnJkcyIpCmBgYAoKCmBgYHtyfQpiY19zblJOQQpgYGAKCkl0J3MgYWx3YXlzIGEgZ29vZCBpZGVhIHRvIHJlcnVuIG5vcm1hbGl6YXRpb24gdG8gbWFrZSBzdXJlIHlvdXIgZGF0YSBpcyBpbiB0aGUgY29ycmVjdCBmb3JtYXQgYmVmb3JlIG1vdmluZyBmb3J3YXJkIHdpdGggaW50ZWdyYXRpb24uIFdlJ3ZlIGFscmVhZHkgcHJlcHJvY2Vzc2VkIHRoaXMgZGF0YXNldC4KCmBgYApiY19zblJOQSA8LSBTQ1RyYW5zZm9ybShiY19zblJOQSwgbmNlbGxzID0gMzAwMCwgdmVyYm9zZSA9IEZBTFNFKSAlPiUgCiAgUnVuUENBKHZlcmJvc2UgPSBGQUxTRSkgJT4lIAogIFJ1blVNQVAoZGltcyA9IDE6MzApCmBgYAoKc25STkEgQ2xhc3MKYGBge3IsIGZpZy53aWR0aD0xMH0KRGltUGxvdChiY19zblJOQSwgZ3JvdXAuYnkgPSAiaWRlbnQiLCBsYWJlbCA9IEZBTFNFKSArCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIiwgImRhcmtncmVlbiIpKQpgYGAKU3ViY2xhc3MKYGBge3IsIGZpZy53aWR0aD0xMH0KRGltUGxvdChiY19zblJOQSwgZ3JvdXAuYnkgPSAic3ViY2xhc3MiLCBsYWJlbCA9IEZBTFNFKQpgYGAKCiMjIHNuUk5BIERpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBHZW5lcwoKV2hhdCBnZW5lcyBkZWZpbmUgc29tZSBjZWxsIHR5cGVzPwoKYGBgCiMgRmluZCBtYXJrZXJzIGZvciBldmVyeSBjbHVzdGVyIGNvbXBhcmVkIHRvIGFsbCByZW1haW5pbmcgY2VsbHMsIHJlcG9ydCBvbmx5IHRoZSBwb3NpdGl2ZSBvbmVzCiMgVGhpcyB0YWtlcyBhIGJpdCBvZiB0aW1lIHNvIHdlJ2xsIHNraXAgaXQgYW5kIG1vdmUgb24gdG8gc3BlY2lmaWMgY2VsbCB0eXBlcwpkZV9tYXJrZXJzX3NuUk5BIDwtIEZpbmRBbGxNYXJrZXJzKGJjX3NuUk5BLCBvbmx5LnBvcyA9IFRSVUUsIG1pbi5wY3QgPSAwLjI1LCBsb2dmYy50aHJlc2hvbGQgPSAwLjI1KQpkZV9tYXJrZXJzX3NuUk5BICU+JSAKICBncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgdG9wX24obiA9IDIsIHd0ID0gYXZnX2xvZ0ZDKQpgYGAKCk5vdGljZSBoZXJlIHRoYXQgd2UgYXJlIHVzaW5nIGB0ZXN0LnVzZSA9ICJyb2MiYCB3aGljaCBpcyBhIEFVQyBjbGFzc2lmaWVyIHdoaWNoIHdpbGwgZ2l2ZSB1cyBhbiBpZGVhIGFzIHRvIGhvdyB3ZWxsIGFueSBnaXZlbiBnZW5lIGRlZmluZXMgYSBjZWxsIHR5cGUuCgoqKkZpbmQgbWFya2VycyB0aGF0IGRlZmluZSBUdW1vciBjZWxscyoqCgpgYGB7ciBlY2hvPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgZmlnLmtlZXA9J2FsbCcsIG1lc3NhZ2UgPSBGQUxTRX0KZGVfbWFya2Vyc190dW1vciA8LSBGaW5kTWFya2VycyhiY19zblJOQSwgaWRlbnQuMSA9ICJMaWtlbHkgdHVtb3IgY2VsbHMiLCBsb2dmYy50aHJlc2hvbGQgPSAwLjI1LCB0ZXN0LnVzZSA9ICJyb2MiLCBvbmx5LnBvcyA9IFRSVUUsIHZlcmJvc2UgPSBGQUxTRSkKYGBgCgoKYGBge3J9CmRlX21hcmtlcnNfdHVtb3IgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImdlbmUiKSAlPiUgCiAgYXJyYW5nZSgtcG93ZXIpCmBgYAoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTB9CihGZWF0dXJlUGxvdChiY19zblJOQSwgZmVhdHVyZXMgPSAiQ0FTQzE1IikgfAogRGltUGxvdChiY19zblJOQSwgZ3JvdXAuYnkgPSAiaWRlbnQiLCBsYWJlbCA9IEZBTFNFKSsKICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiNiMmRmOGEiLCIjZTQxYTFjIiwiIzM3N2ViOCIsIiM0ZGFmNGEiLCIjZmY3ZjAwIiwiZ29sZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNhNjU2MjgiLCAiIzk5OTk5OSIsICJibGFjayIsICJwaW5rIiwgInB1cnBsZSIsICJicm93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ3JleSIsICJ5ZWxsb3ciLCAiZ3JlZW4iLCAiZGFya2dyZWVuIikpKQpgYGAKCioqRmluZCBtYXJrZXJzIHRoYXQgZGVmaW5lIFQgY2VsbHMqKgpgYGB7ciwgZWNobz1GQUxTRSwgcmVzdWx0cz0naGlkZScsIGZpZy5rZWVwPSdhbGwnLCBtZXNzYWdlID0gRkFMU0V9CmRlX21hcmtlcnNfdGNlbGwgPC0gRmluZE1hcmtlcnMoYmNfc25STkEsIGlkZW50LjEgPSAiVCBjZWxscyIsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUsIHRlc3QudXNlID0gInJvYyIsIG9ubHkucG9zID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFKQpgYGAKCgpgYGB7cn0KZGVfbWFya2Vyc190Y2VsbCAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiZ2VuZSIpICU+JSAKICBhcnJhbmdlKC1wb3dlcikKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTEwfQooRmVhdHVyZVBsb3QoYmNfc25STkEsIGZlYXR1cmVzID0gIlNLQVAxIil8CiBEaW1QbG90KGJjX3NuUk5BLCBncm91cC5ieSA9ICJpZGVudCIsIGxhYmVsID0gRkFMU0UpKwogICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiI2IyZGY4YSIsIiNlNDFhMWMiLCIjMzc3ZWI4IiwiIzRkYWY0YSIsIiNmZjdmMDAiLCJnb2xkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiI2E2NTYyOCIsICIjOTk5OTk5IiwgImJsYWNrIiwgInBpbmsiLCAicHVycGxlIiwgImJyb3duIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmV5IiwgInllbGxvdyIsICJncmVlbiIsICJkYXJrZ3JlZW4iKSkpCmBgYAoKKipGaW5kIG1hcmtlcnMgdGhhdCBkZWZpbmUgc3RlbSBjZWxscyoqCmBgYHtyfQpkZV9tYXJrZXJzX3N0ZW1jZWxsIDwtIEZpbmRNYXJrZXJzKGJjX3NuUk5BLCBpZGVudC4xID0gIkNENDlmLWhpIE1hU0NzIiwgbG9nZmMudGhyZXNob2xkID0gMC4yNSwgdGVzdC51c2UgPSAicm9jIiwgb25seS5wb3MgPSBUUlVFLCB2ZXJib3NlID0gRkFMU0UpCmBgYAoKCmBgYHtyfQpkZV9tYXJrZXJzX3N0ZW1jZWxsICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJnZW5lIikgJT4lIAogIGFycmFuZ2UoLXBvd2VyKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTB9CihGZWF0dXJlUGxvdChiY19zblJOQSwgZmVhdHVyZXMgPSAiSUZORy1BUzEiKXwKIERpbVBsb3QoYmNfc25STkEsIGdyb3VwLmJ5ID0gImlkZW50IiwgbGFiZWwgPSBGQUxTRSkrCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIjYTY1NjI4IiwgIiM5OTk5OTkiLCAiYmxhY2siLCAicGluayIsICJwdXJwbGUiLCAiYnJvd24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZXkiLCAieWVsbG93IiwgImdyZWVuIiwgImRhcmtncmVlbiIpKSkKYGBgCgojIyBJZGVudGlmeSBhbmQgVHJhbnNmZXIgQW5jaG9ycwoKYGBge3J9CmFuY2hvcnMgPC0gRmluZFRyYW5zZmVyQW5jaG9ycyhyZWZlcmVuY2UgPSBiY19zblJOQSwgcXVlcnkgPSBicmVhc3RfY2FuY2VyLG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIlNDVCIpCgpwcmVkaWN0aW9ucy5hc3NheSA8LSBUcmFuc2ZlckRhdGEoYW5jaG9yc2V0ID0gYW5jaG9ycywgcmVmZGF0YSA9IGJjX3NuUk5BJHN1YmNsYXNzLCBwcmVkaWN0aW9uLmFzc2F5ID0gVFJVRSwgCiAgICB3ZWlnaHQucmVkdWN0aW9uID0gYnJlYXN0X2NhbmNlcltbInBjYSJdXSkKCmJyZWFzdF9jYW5jZXJbWyJwcmVkaWN0aW9ucyJdXSA8LSBwcmVkaWN0aW9ucy5hc3NheQoKRGVmYXVsdEFzc2F5KGJyZWFzdF9jYW5jZXIpIDwtICJwcmVkaWN0aW9ucyIKYGBgCgpMZXQncyBoYXZlIGEgbG9vayBhdCBvdXIgYW5ub3RhdGlvbnMgYWdhaW4uCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KY2EgPC0gcmVhZGJpdG1hcDo6cmVhZC5iaXRtYXAoIi9tbnQvaG9tZS9zdGVwaGVuLndpbGxpYW1zL3lhcmQvT2Rpbi9TSUJfMjAyMF9Xb3Jrc2hvcC9pbWFnZXMvQnJlYXN0IENhbmNlciBQYXRoLnBuZyIpCiMgaW4gdGhlIHR1dG9yaWFsCiMgY2EgPC0gcmVhZGJpdG1hcDo6cmVhZC5iaXRtYXAoJy9tbnQvbGlicy9zaGFyZWRfZGF0YS9odW1hbl9icmVhc3RfY2FuY2VyXzEvaW1hZ2VzL0JyZWFzdF9DYW5jZXJfUGF0aC5wbmcnKQpwbG90KDA6MSwwOjEsdHlwZT0ibiIsYW5uPUZBTFNFLGF4ZXM9RkFMU0UpCnJhc3RlckltYWdlKGNhLDAsMCwxLDEpCmBgYAoKCiMjIEltbXVuZSBNaWNyb2Vudmlyb25tZW50CiMjIyBUIGNlbGwgc3VidHlwZXMKCl9fTk9URV9fIHdlJ2xsIHdvcmsgd2l0aCBzdWJjbGFzcwoKV2hhdCBhYm91dCB0aGUgaW1tdW5lIG1pY3JvZW52aXJvbm1lbnQ/IAoKRHVjYWwgQ2FyY2lub21hIF9pbiBzaXR1XyBpcyBkZXBsZXRlZCBvZiBULWNlbGxzCmBgYHtyLCBmaWcud2lkdGg9MTUsIHdhcm5pbmc9RkFMU0V9CihTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGMoIlQgY2VsbHMtMCIpLCAKICAgICAgICAgICAgICAgICAgIHB0LnNpemUuZmFjdG9yID0gMS41LCBuY29sID0gMiwgY3JvcCA9IFRSVUUpIHwKU3BhdGlhbEZlYXR1cmVQbG90KGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJUIGNlbGxzLTEiKSwgCiAgICAgICAgICAgICAgICAgICBwdC5zaXplLmZhY3RvciA9IDEuNSwgbmNvbCA9IDIsIGNyb3AgPSBUUlVFKSkgLwooU3BhdGlhbEZlYXR1cmVQbG90KGJyZWFzdF9jYW5jZXIsIAogICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJUIGNlbGxzLTIiKSwgCiAgICAgICAgICAgICAgICAgICBwdC5zaXplLmZhY3RvciA9IDEuNSwgbmNvbCA9IDIsIGNyb3AgPSBUUlVFKSB8ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gYygiVCBjZWxscy01IiksIAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZS5mYWN0b3IgPSAxLjUsIG5jb2wgPSAyLCBjcm9wID0gVFJVRSkpCmBgYAoKIyMjIEIgY2VsbCBzdWJ0eXBlcwoKQiBjZWxscyBhcmUgZW5yaWNoZWQgaW4gdGhlIGZpYnJvdXMgdGlzc3VlIG91dHNpZGUgdGhlIHR1bW9yCmBgYHtyLCBmaWcud2lkdGg9MTUsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gYygiQiBjZWxscyIpLCAKICAgICAgICAgICAgICAgICAgIHB0LnNpemUuZmFjdG9yID0gMS41LCBuY29sID0gMiwgY3JvcCA9IFRSVUUpCmBgYAoKClRoZXJlIHNlZW0gdG8gYmUgc29tZSBkdWN0YWwgY2VsbHMgYnV0IGhhdmUgYSBsb29rIGF0IG91ciBzY29yZS4gQXJlIHdlIGNvbmZpZGVudCBpbiB0aGlzIGFzc2VydGlvbj8gCmBgYHtyLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gYygiRHVjdGFsIGNlbGxzIiksIAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZS5mYWN0b3IgPSAxLjUsICBjcm9wID0gVFJVRSkKYGBgCiMjIFR1bW9yIFN1YnR5cGVzCgpJdCBsb29rcyBsaWtlIHRoZSBkdWNhbCBjYXJjaW5vbWEgX2luIHNpdHVfIGlzIGVucmljaGVkIGZvciB0dW1vciBzdWJ0eXBlcyA4LCAxMCwgYW5kIDEyIGJ1dCBub3QgMy4gCmBgYHtyLCBmaWcud2lkdGg9MTUsIHdhcm5pbmc9RkFMU0V9CihTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGMoIlR1bW9yIGNlbGxzLTMiKSwgCiAgICAgICAgICAgICAgICAgICBwdC5zaXplLmZhY3RvciA9IDEuNSwgbmNvbCA9IDIsIGNyb3AgPSBUUlVFKSB8ClNwYXRpYWxGZWF0dXJlUGxvdChicmVhc3RfY2FuY2VyLCAKICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gYygiVHVtb3IgY2VsbHMtOCIpLCAKICAgICAgICAgICAgICAgICAgIHB0LnNpemUuZmFjdG9yID0gMS41LCBuY29sID0gMiwgY3JvcCA9IFRSVUUpKSAvCihTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGMoIlR1bW9yIGNlbGxzLTEwIiksIAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZS5mYWN0b3IgPSAxLjUsIG5jb2wgPSAyLCBjcm9wID0gVFJVRSkgfApTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGMoIlR1bW9yIGNlbGxzLTEyIiksIAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZS5mYWN0b3IgPSAxLjUsIG5jb2wgPSAyLCBjcm9wID0gVFJVRSkpCmBgYAoKTGlrZSB0aGUgRHVjdGFsIENlbGxzLCB3ZSBtaWdodCBub3QgYmUgYXMgY29uZmlkZW50IGluIHRoZSBUdW1vciBTdGVtIENlbGxzIGJ1dCB0aGlzIG1pZ2h0IG1ha2Ugc2Vuc2UgY29uc2lkZXJpbmcgdGhlIDEweCBzblJOQSBkYXRhc2V0IGFuZCB0aGUgVmlzaXVtIGRhdGFzZXQgYXJlIGZyb20gZGlmZmVyZW50IGluZGl2aWR1YWxzLgpgYGB7ciwgZmlnLndpZHRoPTEwLCB3YXJuaW5nPUZBTFNFfQpTcGF0aWFsRmVhdHVyZVBsb3QoYnJlYXN0X2NhbmNlciwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IGMoIkNENDlmLWhpIE1hU0NzIiksIAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZS5mYWN0b3IgPSAxLjUsY3JvcCA9IFRSVUUpICsgCnBsb3RfYW5ub3RhdGlvbigKICB0aXRsZSA9ICdUdW1vciBTdGVtIENlbGxzJykKYGBgCg==